#include "Gui.h"
#include <stdlib.h>

Gui* Gui::singleton = 0;
const int Gui::maxNumDataRecords = 10;
const int Gui::lineBeginningOfData = 5;
const int Gui::infoLine = 2;
const int Gui::nodeLine = 3;
const int Gui::lineReply = 17;
const int Gui::lineLegend = 12;
const int Gui::lineInput = 19;
const int Gui::refreshPeriod = 1000; //ms
int Gui::numFields = 0;
//bool Gui::toggleInfo=true;

using namespace std;

void Gui::display(const unsigned int len, const void* msg) {
	if(len != sizeof(StatusData) || msg == 0) return;
	lastDataInput.first = len;
	lastDataInput.second = const_cast<void*>(msg);

	void* helpPointer = const_cast<void*>(msg);
	//Explanation of this part:
	//For every data field to be displayed the pointer is casted to the type of the expected data field
	//and then printed. Afterwards the casted(!) pointer is incremented by one so it points to the beginning
	//of the next data field.
	for(int i = 0; i < numFields; ++i) {
		movePointer(i);
		switch(description.type[i]) {
		case DTCHAR:
			PRINTF("%c", *((char*)helpPointer));
			helpPointer = ((char*)helpPointer)+1;
			break;
		case DTBOOL:
			PRINTF( *((bool*)helpPointer) ? "true" : "false");
			helpPointer = ((bool*)helpPointer)+1;
			break;
		case DTSHORT:
			PRINTF("%d", *((short*)helpPointer));
			helpPointer = ((short*)helpPointer)+1;
			break;
		case DTLONG:
			PRINTF("%ld", *((long*)helpPointer));
			helpPointer = ((long*)helpPointer)+1;
			break;
		case DTLONGLONG:
			PRINTF("%lld", *((long long*)helpPointer));
			helpPointer = ((long long*)helpPointer)+1;
			break;
		case DTFLOAT:
			PRINTF("%g", *((float*)helpPointer));
			helpPointer = ((float*)helpPointer)+1;
			break;
		case DTDOUBLE:
			PRINTF("%g", *((double*)helpPointer));
			helpPointer = ((double*)helpPointer)+1;
			break;
		case DTSTRING:
			PRINTF("%s", *((char**)helpPointer));
			helpPointer = ((char**)helpPointer)+1;
			break;
		default:
			break;
		}
	}
}

	//Moves the Pointer to the needed position for displaying a specific data field
	//index tells the index of the dataField-array that describes the data field
void Gui::movePointer(int index) {
	PRINTF(SCREEN_MOVE_CURSOR_TO, lineBeginningOfData+index, strlen(description.names[index])+3);
}


Gui::Gui(DataDescription dd) : Thread("GUI"), wStatusSubscriber(satelliteStatus,wStatusBuffer) {
	//Singleton initialisation
	if(!singleton) {
		singleton = this;
	}

	//Analyzes ans stores the data description
	singleton->description = dd;
	for(;numFields<MAX_NUM_STATUS_DATA_FIELDS && dd.names[numFields] != 0;++numFields);

}

void Gui::init(void) {
	//Guard. Only the singleton should run
	if(this != singleton) return;

	//Sets up the activation beats
	this->setPeriodicBeat(NOW()+refreshPeriod*MILLISECONDS, refreshPeriod*MILLISECONDS);
	toggleInfo=true;
}

void Gui::run(void) {
	//Guard. Only the singleton should run
	if(this != singleton) return;

	//Variables needed later
	//this one is for storing the houskeeping data temporarily
	StatusData temp;
	
	//Display until programm termination
	while(1) {

		//Get the current status data and display it
		wStatusBuffer.get(temp);
		this->setStatus(sizeof(StatusData),(void*)&temp);


		//read data from satellite interface
		//get number of all nodes (functioning and non-functioning)
		numNodes = satelliteInterface.getNumNodes();
		//get number of non-functioning nodes
		numDisabledNodes = satelliteInterface.getNumUnresponsiveNodes();
		//get number of functioning nodes
		numResponsiveNodes = satelliteInterface.getNumResponsiveNodes();
		//get the ID of the worker node
		workerID = satelliteInterface.getWorkerID();
		//mark the worker
		this->setWorker(workerID);


		//display all functioning nodes
		nodeIDList = satelliteInterface.getResponsiveNodes();
		this->setNodeList(numResponsiveNodes,nodeIDList);


		//refresh the display
		singleton->refresh();

		//suspend thread until next beat
		waitUntilNextBeat();
	}
}

//Inherited from Putter, needs pointer to data struct
void Gui::setStatus(const unsigned int len, const void* msg) {
	if(len != sizeof(StatusData)) return;

	//store the data. usefull when refresh() is called before new data arrived
	singleton->lastDataInput.first = len;
	singleton->lastDataInput.second = const_cast<void*>(msg);
}

void Gui::setInfoMessage(const char* msg) {
	singleton->statusMessage = msg;
	//It will be printed the next time refresh() is called
}

void Gui::setNodeList(int len, int* IDs) {
	//clear the nodelist
	singleton->nodeList.clear();

	//store the new node list
	for(int i = 0; i < len; ++i) {
		singleton->nodeList.push_back(IDs[i]);
	}
}

void Gui::setWorker(int ID) {
	singleton->workerID = ID;
}

void Gui::setConsoleInput(const char* msg){
	singleton->consoleInput = msg;
}

void Gui::setReply(const char* msg){
	singleton->reply = msg;
}

void Gui::setToggleInfo(){
	singleton->toggleInfo= !singleton->toggleInfo;
}


//Redraws the interface
void Gui::refresh() {
	//Clear the screen
	PRINTF(SCREEN_CLEAR);

	singleton->display(singleton->lastDataInput.first, singleton->lastDataInput.second);

	//Move the cursor to upper left corner and write title
	PRINTF(SCREEN_MOVE_CURSOR_TO,1,1);
	PRINTF("EGSE\n");

	//Move cursor to infoline and write status message
	PRINTF(SCREEN_MOVE_CURSOR_TO,infoLine,1);
	if(!singleton->statusMessage.empty()) PRINTF("last input: %s\n", singleton->statusMessage.c_str());

	//Move cursor to the node line and print the title and the nodes
	PRINTF(SCREEN_MOVE_CURSOR_TO, nodeLine, 1);
	PRINTF("Active nodes: ");
	//Iterate the nodelist and print every node.
	for(unsigned int i = 0; i < singleton->nodeList.size(); ++i) {
		//if the current node is the worker it is printed with an asterisk and brackets
		if(!(singleton->nodeList[i] == workerID))
			PRINTF("%d", singleton->nodeList[i]);
		else
			PRINTF("%d(*)", singleton->nodeList[i]);

		//print a colon after every node except the last
		if(i < singleton->nodeList.size()-1) PRINTF(", ");
	}

	//Move to the line where data printing begins and print the descriptions (every description in a new line)
	PRINTF(SCREEN_MOVE_CURSOR_TO, lineBeginningOfData, 1);
	for(int i = 0; i < numFields; ++i) {
		PRINTF("%s: \n", singleton->description.names[i]);
	}

	//Print further information about the nodes. TODO: Maybe remove or move some of this
	PRINTF("\n");
	PRINTF("Disabled Nodes: %i \n", numDisabledNodes);
	PRINTF("Responsive Nodes: %i \n", numResponsiveNodes);

	//Prints the reply regarding consoleinput
	PRINTF(SCREEN_MOVE_CURSOR_TO, lineReply);
	PRINTF("%s",singleton->reply.c_str());
	//Prints the already inputted characters, to be displayed after "PRINTF(SCREEN_CLEAR);"
	PRINTF(SCREEN_MOVE_CURSOR_TO, lineInput);
	PRINTF("%s",singleton->consoleInput.c_str());
	//Prints the togglable infoline's
	if(toggleInfo){
		PRINTF(SCREEN_MOVE_CURSOR_TO, lineLegend);
		PRINTF("i: toggle info\tp:send ping\tu:update satellite\tgw: get worker ID\ngm: get mode\tswi: set worker(to id i)\tsmx:switch mode(to mode x)\nspb: set publishing, b= t(true) / f(false) \nsspnum:set status period(0<num<9999999)");
	}
}

Gui* Gui::getSingletonPtr(){
	return singleton;
}
